#  arp.rb
#
#  This file is part of myvnet.
#
#  Copyright (C) 2008  Filipe David Borba Manana,  fdmanana@gmail.com
#
#  myvnet is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  myvnet is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with myvnet.  If not, see <http://www.gnu.org/licenses/>.


require 'lib/protocols/generic_protocol'
require 'lib/protocols/ipv4'
require 'lib/protocols/ethernet'
require 'lib/exceptions'


# Address Resolution Protocol (ARP) module.
#
module ARP
  
  # maps htype (hardware type) codes to symbols
  HTYPE_SYM = {
    0x00_01 => :ethernet,
  }.freeze
  
  # maps htype (hardware type) symbols to codes
  HTYPE_CODE = HTYPE_SYM.invert.freeze
  
  
  # maps ptype (protocol type) codes to symbols
  PTYPE_SYM = {
    0x08_00 => :ipv4,
  }
    
  # maps ptype (protocol type) symbols to codes
  PTYPE_CODE = PTYPE_SYM.invert.freeze
  
  
  # maps oper (operation) codes to symbols
  OPER_SYM = {
    0x00_01 => :request,
    0x00_02 => :reply,
  }.freeze
  
  # maps oper (operation) symbols to codes
  OPER_CODE = OPER_SYM.invert.freeze
  
  
  
  # ARP packet.
  #
  class Packet < GenericProtocol::Packet
    
    @packet_type = :arp
    @layer = 3
    
    attr_accessor :htype # hardware type (datalink layer)
    attr_accessor :ptype # protocol type (network layer)
    attr_accessor :hlen  # hardware address length (in bytes)
    attr_accessor :plen  # protocol address length (in bytes)
    attr_accessor :oper  # operation    
    attr_accessor :sha   # sender hardware address
    attr_accessor :spa   # sender protocol address
    attr_accessor :tha   # target hardware address
    attr_accessor :tpa   # target protocol address
    
    
    def initialize(bstream = nil)
      super
      
      if bstream.nil?
        bstream = "\x00\x01\x08\x00\x06\x04\x00\x01"
        bstream << ("\x00\x00\x00\x00\x00\x00" * 2)
        bstream << ("\x00\x00\x00\x00" * 2)
      end
      
      @htype = (bstream[0,2].unpack 'n')[0]
      @htype = HTYPE_SYM[@htype] || @htype
      
      @ptype = (bstream[2,2].unpack 'n')[0]
      @ptype = PTYPE_SYM[@ptype] || @ptype
      
      @hlen = (bstream[4,1].unpack 'C')[0]
      @plen = (bstream[5,1].unpack 'C')[0]
      @oper = OPER_SYM[ (bstream[6,2].unpack 'n')[0] ]
      
      case @htype
        when :ethernet
          begin
            @sha = Ethernet.to_friendly_addr(bstream[8,6])
            @tha = Ethernet.to_friendly_addr(bstream[(8 + 6 + @plen),6])
          rescue
            raise BadArgFormat, "Invalid Ethernet address(es) in the byte stream"
          end
        else
          @sha = bstream[8, @hlen]
          @tha = bstream[(8 + @hlen + @plen), @hlen]
      end
      
      case @ptype
        when :ipv4
          begin
            @spa = IPv4.to_friendly_addr(bstream[(8 + @hlen),4])
            @tpa = IPv4.to_friendly_addr(bstream[(8 + @hlen + 4 + @hlen),4])
          rescue
            raise BadArgFormat, "Invalid IPv4 address(es) in the byte stream"
          end
        else
          @spa = bstream[(8 + @hlen), @plen]
          @tpa = bstream[(8 + @hlen + @plen + @hlen), @plen]
      end      
    end
    
    
    def initialize_copy(orig)
      @sha = orig.sha.dup if orig.sha.is_a? String
      @spa = orig.spa.dup if orig.spa.is_a? String
      @tha = orig.tha.dup if orig.tha.is_a? String
      @tpa = orig.tpa.dup if orig.tpa.is_a? String
    end
    
    
    def to_bstream
      stream = ''
      
      if @htype.is_a? Symbol
        stream << ( [ HTYPE_CODE[@htype] ].pack 'n' )
      else
        stream << ( [@htype].pack 'n' )
      end
    
      if @ptype.is_a? Symbol
        stream << ( [ PTYPE_CODE[@ptype] ].pack 'n' )
      else
        stream << ( [@ptype].pack 'n' )
      end

      stream << ( [ @hlen ].pack 'C')
      stream << ( [ @plen ].pack 'C')
      stream << ( [ OPER_CODE[@oper] ].pack 'n' )
      
      case @htype
        when :ethernet
          stream << Ethernet.to_bstream_addr(@sha)
        else
          stream << @sha
      end

      case @ptype
        when :ipv4
          stream << IPv4.to_bstream_addr(@spa)
        else
          stream << @spa
      end

      case @htype
        when :ethernet
          stream << Ethernet.to_bstream_addr(@tha)
        else
          stream << @tha
      end

      case @ptype
        when :ipv4
          stream << IPv4.to_bstream_addr(@tpa)
        else
          stream << @tpa
      end
      
      stream
    end
                
  end
  
end